#include "SQLTable.h"
#include "TableInfo.h"
#include "DBFile.h"
#include "SQLError.h"
#include <vector>
#include <algorithm>

extern DB_FILE_ENTRY db_file_entry[DB_MAX_FILENUM];
DB_FILEHEADER * GetFileHeader(int fileid)
{
	return &(db_file_entry[fileid].fileheader);
}

inline bool operator < (const Slot & lhs, const Slot & rhs)
{
	if (lhs.offset < rhs.offset)
		return true;
	else
		return false;
}

SQLTable::SQLTable(void)
{
	tableName = "";
	fd = -1;
	tableInfo = NULL;
}

int SQLTable::OpenTable()
{
	tableInfo = GetTableInfo(tableName);

	if (tableInfo == NULL)
		throw TableNotOpen(tableName, "Table not created");
	
	if (fd < 0)
	{
		const char * fileName = tableInfo->fileName.c_str();
		if ((fd = DBOpenFile(fileName)) < 0)
		{
			fd = -1;
			throw TableNotOpen(tableName, "File Open Error");
		}
	}
	return fd;
}

SQLTable::Iterator & SQLTable::InsertRecord(int size, char * data)
{
	if (tableInfo == NULL)
		throw TableNotOpen(tableName, "when insert record");
	DB_FILEHEADER * fileHeader = GetFileHeader(fd);
	int pId = -1, err = 0;
	DB_PAGE * newPage = NULL, * firstPage = NULL;;
	int pageId;

	if (tableInfo->firstPageId < 0)
	{
		err = AllocatePage(fd, &pageId, &newPage);
		if (err < 0)
			throw PageError(tableName, err, "Cannot Allocate");
		newPage->pgheader.prevpage = -1;
		newPage->pgheader.nextpage = -1;
		tableInfo->firstPageId = pageId;
	}

	err = GetPage(fd, tableInfo->firstPageId, &firstPage);
	if (err < 0)
		throw PageError(tableName, err, "Cannot Get First Page");

	for (int i = 0; i < DB_MAX_FILEPAGE; i ++)
		if (fileHeader->fixed[i] > size)
		{
			pId = i;
			break;
		}

	if (pId == -1)
	{
		err = AllocatePage(fd, &pageId, &newPage);
		if (err < 0)
			throw PageError(tableName, err, "Cannot Allocate");
		newPage->pgheader.prevpage = -1;
		newPage->pgheader.nextpage = tableInfo->firstPageId;
		firstPage->pgheader.prevpage = pageId;
		tableInfo->firstPageId = pageId;
		pId = pageId;
	}
	else
	{
		err = GetPage(fd, pId, &newPage);
		if (err < 0)
			throw PageError(tableName, err, "Cannot Get Page");
	}
	if (newPage->pgheader.flagBits)
		Defragmentation(pId);
	SQLPage * page = (SQLPage *)newPage;
	short * slots = &page->slots;
	int freeSlot; 
	for (freeSlot = 0; freeSlot < page->slotNum; freeSlot ++)
		if (*(slots - freeSlot) == -1)
		{
			*(slots - freeSlot) = (short)page->pgheader.freeData;
			break;
		}
	if (fileHeader->fixed[pId] != page->pgheader.freeCnt)
		std::cerr << "Fatal : free count is not consistent!!!" << std::endl;
	page->pgheader.freeData += size;
	fileHeader->fixed[pId] -= size;
	if (freeSlot == page->slotNum - 1)
	{
		if (fileHeader->fixed[pId] < 3)
			fileHeader->fixed[pId] = 1;
		else
		{
			fileHeader->fixed[pId] -= 2;
			*(slots - page->slotNum) = -1;
			page->slotNum ++;
		}
		page->pgheader.slotCnt = page->slotNum;
	}
	page->pgheader.freeCnt = fileHeader->fixed[pId];
	memmove(page->pgdata + *(slots - freeSlot), data, size);
	DBUpdateFileHeader(fd, *fileHeader, TRUE);
	DBFilePageChanged(fd, pId ,TRUE);
	newSlot.SetRid(pId, freeSlot);
	return newSlot;
}

int SQLTable::Defragmentation(int pId)
{
	std::vector<Slot> slotList;
	DB_PAGE * page;
	int error;
	error = GetPage(fd, pId, &page);
	if (error < 0)
		throw PageError(tableName, error, "Can not get page during defragmentation");
	//std::cerr << "Defraging" << std::endl;
	// If the page does not contain fragment , just return 1
	if (!page->pgheader.flagBits)
		return 1;
	short * slots = &(((SQLPage *)page)->slots);
	short slotNum = ((SQLPage *)page)->slotNum;
	for (int i = 0; i < slotNum; i ++)
		if (*(slots - i) != -1)
		{
			slotList.push_back(Slot(i, *(slots - i)));
		}
	std::sort(slotList.begin(), slotList.end());
	char * dst = page->pgdata;
	char * src = NULL;
	int len = 0, off = 0;
	for (size_t i = 0; i < slotList.size(); i ++)
	{
		src = page->pgdata + slotList[i].offset;
		len = *(short *)(src + 1);
		memmove(dst, src, len);
		*(slots - slotList[i].slotNum) = off;
		dst += len;
		off += len;
	}
	page->pgheader.freeData = off;
	page->pgheader.flagBits = 0;
	DBFilePageChanged(fd, pId ,TRUE);
	return 0;
}

int SQLTable::DeleteRecord(const Iterator & rId)
{
	DB_PAGE * page;
	DB_FILEHEADER * fileHeader = GetFileHeader(fd);
	int error;
	error = GetPage(fd, rId.pageId, &page);
	if (error < 0)
		throw PageError(tableName, error, "Can not get page when deleting a record");
	short * slots = &(((SQLPage *)page)->slots);
	short slotNum = ((SQLPage *)page)->slotNum;
	if (rId.slotId >= slotNum)
		throw InvalidRecordId(tableName, " Exceed maximum slot nubmer ");
	if (*(slots - rId.slotId) == -1)
		throw InvalidRecordId(tableName, " record has already been removed ");
	short len = *(short *)(page->pgdata + *(slots - rId.slotId) + 1);
	*(slots - rId.slotId) = -1;
	fileHeader->fixed[rId.pageId] += len;
	page->pgheader.freeCnt = fileHeader->fixed[rId.pageId];
	page->pgheader.flagBits = 1;
	DBFilePageChanged(fd, rId.pageId ,TRUE);
	DBUpdateFileHeader(fd, *fileHeader, TRUE);
	return 0;
}

int SQLTable::AllocatePage(int fileid, int * pageid, DB_PAGE **buf)
{
	int error;
	int i;
	DB_PAGE* page;
	DB_FILEHEADER fileheader;

	if(!DBInvalidFileID(fileid))
	{
		return DB_ERROR_INVALID_FILE;
	}
	fileheader = DBGetFileHeader(fileid);
	if (fileheader.firstfreepage != DB_PAGE_LIST_END)
	{
		*pageid = fileheader.firstfreepage;
		if ((error = BufRead(fileid, *pageid, &page)) != DB_OK)
			return error;
		for(i=fileheader.firstfreepage; i<fileheader.pagecnt; i++)
		{
			if(fileheader.fixed[i] == FALSE)
			{
				fileheader.firstfreepage = i;
				break;
			}
		}
		if(i == fileheader.pagecnt)
			fileheader.firstfreepage = DB_PAGE_LIST_END;
	}
	else 
	{
		*pageid = fileheader.pagecnt;
		if ((error = BufAlloc(fileid, *pageid, &page)) != DB_OK)
			return error;
		fileheader.pagecnt++;
		//fileheader.firstfreepage = *pageid;

		if ((error = DBFilePageChanged(fileid, *pageid, TRUE)) != DB_OK)
		{
			printf("Interal error: DBAllocPage()\n");
			exit(1);
		}
	}
	fileheader.fixed[*pageid] = DB_PGSIZE - 4;
	DBUpdateFileHeader(fileid, fileheader, TRUE);
	memset(page,0,sizeof(DB_PGHEADER));
	page->pgheader.pageID = *pageid;
	page->pgheader.slotCnt = 1;
	page->pgheader.freeCnt = DB_PGSIZE - 4;
	((SQLPage *)page)->slotNum = 1;
	((SQLPage *)page)->slots = -1;
	*buf = page;
	return DB_OK;
}

int SQLTable::GetPage(int fileid, int pageid, DB_PAGE ** buf)
{
	int error;
	DB_PAGE* page;

	if(!DBInvalidFileID(fileid))
	{
		return DB_ERROR_INVALID_FILE;
	}
	if(!DBInvalidPageID(fileid, pageid))
	{
		return DB_ERROR_INVALID_PAGE;
	}
	if((error = BufRead(fileid, pageid, &page)) != DB_OK)
	{
		if(error = DB_ERROR_PAGE_USED)
			*buf = page;
		return error;
	}
	*buf = page;
	return DB_OK;
}

int SQLTable::GetRecord(SQLTable::Iterator & rId, RowStructure & row)
{
	int error;
	DB_PAGE * page;
	if ((error = GetPage(fd,rId.pageId,&page)) != DB_OK)
		return error;
	SQLPage * sqlPage = (SQLPage *)page;
	short * slots = &sqlPage->slots;
	char * data = (page->pgdata + *(slots - rId.slotId));
	row.ParseColumns(data,tableName);
	return 0;
}

bool SQLTable::GetNextRecord(SQLTable::Iterator & rId, std::map<std::string,std::string> & result)
{
	int error;
	DB_PAGE * page;
	short slotNum, startSlot;
	short * slots;
	char * data = NULL;
	RowStructure row;
	if ((error = GetPage(fd, rId.pageId, &page)) != DB_OK)
		throw PageError(tableName, error, " Get the next record");
	SQLPage * sqlPage = (SQLPage *)page;
	slots = &sqlPage->slots;
	slotNum = sqlPage->slotNum;

	startSlot = rId.slotId + 1;

	int pId = rId.pageId;
	while (1)
	{
		for (int i = startSlot; i < slotNum; i++)
		if (*(slots - i) != -1)
		{
			data = (page->pgdata + *(slots - i));
			row.ParseColumns(data, tableName);
			result = row.GetColumns();
			rId.SetRid(pId, i);
			return true;
		}
		pId = page->pgheader.nextpage;
		if (pId == -1)
			return false;
		error = GetPage(fd,pId,&page);
		if (error < 0)
			throw PageError(tableName, error, "Cannot Get Page while get the next record");
		slotNum = ((SQLPage *)page)->slotNum;
		slots = &((SQLPage *)page)->slots;
		startSlot = 0;
	}
	return false;
}

int SQLTable::SelectAllRecord(std::vector<std::map<std::string,std::string>> & rowSet)
{
	int pId = tableInfo->firstPageId, slotNum = 0, err = 0;
	short * slots = NULL;
	DB_PAGE * page = NULL;
	char * data = NULL;
	RowStructure row;
	while (pId != -1)
	{
		err = GetPage(fd,pId,&page);
		if (err < 0)
			throw PageError(tableName, pId, "Cannot Get Page while select all records");
		slotNum = ((SQLPage *)page)->slotNum;
		slots = &((SQLPage *)page)->slots;
		for (int i = 0; i < slotNum; i++)
		if (*(slots - i) != -1)
		{
			data = (page->pgdata + *(slots - i));
			row.ParseColumns(data, tableName);
			rowSet.push_back(row.GetColumns());
		}
		pId = page->pgheader.nextpage;
	}
	return 0;
}

int SQLTable::Flush()
{
	return FileRelease(fd);
}

int SQLTable::CloseTable()
{
	return DBCloseFile(fd);
}

SQLTable::~SQLTable(void)
{
}
